package kubernetes

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strconv"

	"github.com/golang/glog"
	"github.com/gorilla/mux"

	authapi "eccgateway/pkg/api/auth"
	endedgecloud "eccgateway/pkg/api/endedgecloud/v1"
	"eccgateway/pkg/util"
)

type BaseKubernetesController struct {
	clusterCache util.LRUCache
}

var BaseKubernetesControllerImpl BaseKubernetesController

const (
	defaultClusterCacheLength = 10
)

func init() {
	BaseKubernetesControllerImpl = BaseKubernetesController{clusterCache: util.Constructor(defaultClusterCacheLength)}
}

func (c BaseKubernetesController) GetCluster(clusterName string) (*endedgecloud.Cluster, error) {
	clusterInterface, ok := c.clusterCache.Get(clusterName)
	if ok {
		cluster, ok1 := clusterInterface.(*endedgecloud.Cluster)
		if ok1 {
			return cluster, nil
		}
	}

	cluster, err := util.GetCluster(clusterName)
	if err == util.NotFoundError {
		glog.Errorf("get cluster[%v] from kubernetes failed, err: %v", clusterName, err)
		return nil, err
	}
	if err != nil {
		glog.Errorf("get cluster[%v] from kubernetes failed, err: %v", clusterName, err)
		return nil, err
	}

	c.clusterCache.Put(clusterName, cluster)

	return cluster, nil
}

func (c BaseKubernetesController) List(kind string,
	labelSelectorFunc func(oldLabelSelectors map[string]string, info *authapi.RespToken) map[string]string,
	w http.ResponseWriter, r *http.Request) {
	info, err := util.GetUserInfo(r.Header.Get(authapi.ParseInfo))
	if err != nil {
		glog.Errorf("get user info from header failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, err.Error())
		return
	}

	q := r.URL.Query()
	labelSelectors := parseLabelSelector(q.Get(labelSelector))

	labelSelectors = labelSelectorFunc(labelSelectors, info)

	q.Del(labelSelector)
	q.Add(labelSelector, constructLabelSelector(labelSelectors))
	r.URL.RawQuery = q.Encode()

	(&KubernetesController{}).ServeHTTP(w, r)
}

func (c BaseKubernetesController) Create(kind string,
	construction func(reqBody []byte, username, userID, groupID string) ([]byte, string, string, error),
	isAllowedOperation func(info *authapi.RespToken, largestRole authapi.RoleType, clusterGroupID, username, userID, groupID string) bool,
	w http.ResponseWriter, r *http.Request) {
	info, err := util.GetUserInfo(r.Header.Get(authapi.ParseInfo))
	if err != nil {
		glog.Errorf("get user info from header failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, err.Error())
		return
	}

	requestBody, err := ioutil.ReadAll(r.Body)
	if err != nil {
		glog.Errorf("request body read failed, %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, fmt.Sprintf("request body read failed, %v", err))
		return
	}

	newRequestBody, clusterName, resourceName, err := construction(requestBody, info.Name, info.UserID, strconv.Itoa(info.Group.ID))
	if err != nil {
		glog.Errorf("reconstruction request body failed, %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, fmt.Sprintf("reconstruction request body failed, %v", err))
		return
	}

	if clusterName == "" {
		glog.Errorf("create %v failed, the resource does not specify which cluster it belongs to, user: %v/%v, resource name: %v", kind, info.Name, info.UserID, resourceName)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, fmt.Sprintf("create %v failed, the resource does not specify which cluster it belongs to", kind))
		return
	}
	cluster, err := c.GetCluster(clusterName)
	if err == util.NotFoundError {
		glog.Errorf("get cluster from kubernetes failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusNotFound, err.Error())
		return
	}
	if err != nil {
		glog.Errorf("get cluster from kubernetes failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusInternalServerError, err.Error())
		return
	}

	namespaceList, err := util.GetNamespaceListByCluster(clusterName)
	if err == util.NotFoundError {
		glog.Errorf("get namespace list from kubernetes failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusNotFound, err.Error())
		return
	}
	if err != nil {
		glog.Errorf("get namespace list from kubernetes failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusInternalServerError, err.Error())
		return
	}

	isInCluster := false
	namespace := mux.Vars(r)["namespace"]
	for _, ns := range namespaceList.Items {
		if ns.Name == namespace {
			isInCluster = true
			break
		}
	}
	if !isInCluster {
		errorMessage := fmt.Sprintf("no permission to create %v in namespace %v", kind, namespace)
		glog.Errorf("%v, request user: %v/%v, user group: %v cluster: %v, cluster group: %v", errorMessage,
			info.Name, info.UserID, info.Group.ID,
			clusterName, cluster.Labels[groupIDKey])
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, errorMessage)
		return
	}

	largestRole := authapi.GetLargestRolePermission(info.Role)
	if !isAllowedOperation(info, largestRole, cluster.Labels[groupIDKey], info.Name, info.UserID, strconv.Itoa(info.Group.ID)) {
		errorMessage := fmt.Sprintf("no permission to create %v in cluster %v", kind, clusterName)
		glog.Errorf("%v, request user: %v/%v, user group: %v, user role: %v, cluster: %v, cluster group: %v", errorMessage,
			info.Name, info.UserID, info.Group.ID, largestRole,
			clusterName, cluster.Labels[groupIDKey])
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, errorMessage)
		return
	}

	req, err := http.NewRequest(r.Method, r.URL.String(), bytes.NewBuffer(newRequestBody))
	if err != nil {
		glog.Errorf("construct request failed, %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusInternalServerError, fmt.Sprintf("construct request failed, %v", err))
		return
	}
	req.RequestURI = r.RequestURI

	(&KubernetesController{}).ServeHTTP(w, req)
}

func (c BaseKubernetesController) Get(kind string,
	getResourceFunc func(name, namespace string) (interface{}, string, string, string, error),
	isAllowedOperation func(info *authapi.RespToken, largestRole authapi.RoleType, username, userID, groupID string) bool,
	w http.ResponseWriter, r *http.Request) {
	info, err := util.GetUserInfo(r.Header.Get(authapi.ParseInfo))
	if err != nil {
		glog.Errorf("get user info from header failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, err.Error())
		return
	}
	largestRole := authapi.GetLargestRolePermission(info.Role)

	name := mux.Vars(r)["name"]
	namespace := mux.Vars(r)["namespace"]
	resource, username, userID, groupID, err := getResourceFunc(name, namespace)
	if err == util.NotFoundError {
		glog.Errorf("get %v from kubernetes failed, err: %v", kind, err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusNotFound, err.Error())
		return
	}
	if err != nil {
		glog.Errorf("get %v from kubernetes failed, err: %v", kind, err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusInternalServerError, err.Error())
		return
	}

	if !isAllowedOperation(info, largestRole, username, userID, groupID) {
		errorMessage := fmt.Sprintf("no permission to get %v %v/%v", kind, namespace, name)
		glog.Errorf("%v, group: %v, creator: %v/%v, request user: %v/%v, user group: %v, user role: %v", errorMessage,
			groupID, username, userID, info.Name, info.UserID, info.Group.ID, largestRole)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, errorMessage)
		return
	}

	respBody, _ := json.Marshal(resource)
	w.WriteHeader(http.StatusOK)
	_, _ = w.Write(respBody)
}

func (c BaseKubernetesController) Delete(kind string,
	getResourceFunc func(name, namespace string) (interface{}, string, string, string, error),
	isAllowedOperation func(info *authapi.RespToken, largestRole authapi.RoleType, username, userID, groupID string) bool,
	w http.ResponseWriter, r *http.Request) {
	info, err := util.GetUserInfo(r.Header.Get(authapi.ParseInfo))
	if err != nil {
		glog.Errorf("get user info from header failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, err.Error())
		return
	}
	largestRole := authapi.GetLargestRolePermission(info.Role)

	name := mux.Vars(r)["name"]
	namespace := mux.Vars(r)["namespace"]
	if largestRole == authapi.OpServiceRole {
		glog.Infof("start delete %v, name: %v/%v, user: %v/%v, user role: %v", kind, namespace, name, info.Name, info.UserID, largestRole)
		(&KubernetesController{}).ServeHTTP(w, r)
		return
	}

	_, username, userID, groupID, err := getResourceFunc(name, namespace)
	if err == util.NotFoundError {
		glog.Errorf("get %v from kubernetes failed, err: %v", kind, err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusNotFound, err.Error())
		return
	}
	if err != nil {
		glog.Errorf("get %v from kubernetes failed, err: %v", kind, err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusInternalServerError, err.Error())
		return
	}
	if isAllowedOperation(info, largestRole, username, userID, groupID) {
		glog.Infof("start delete %v, name: %v/%v, user: %v/%v, user role: %v", kind, namespace, name, info.Name, info.UserID, largestRole)
		(&KubernetesController{}).ServeHTTP(w, r)
		return
	}

	errorMessage := fmt.Sprintf("no permission to delete %v %v/%v", kind, namespace, name)
	glog.Errorf("%v, group: %v, creator: %v/%v, request user: %v/%v, user group: %v, user role: %v", errorMessage,
		groupID, username, userID, info.Name, info.UserID, info.Group.ID, largestRole)
	util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, errorMessage)
	return
}

func (c BaseKubernetesController) Update(kind string,
	getResourceFunc func(name, namespace string) (interface{}, string, string, string, error),
	reconstruction func(oldResource interface{}, reqBody []byte, username, userID, groupID string) ([]byte, error),
	isAllowedOperation func(info *authapi.RespToken, largestRole authapi.RoleType, username, userID, groupID string) bool,
	w http.ResponseWriter, r *http.Request) {
	info, err := util.GetUserInfo(r.Header.Get(authapi.ParseInfo))
	if err != nil {
		glog.Errorf("get user info from header failed, err: %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, err.Error())
		return
	}
	largestRole := authapi.GetLargestRolePermission(info.Role)

	name := mux.Vars(r)["name"]
	namespace := mux.Vars(r)["namespace"]
	old, username, userID, groupID, err := getResourceFunc(name, namespace)
	if err == util.NotFoundError {
		glog.Errorf("get %v from kubernetes failed, err: %v", kind, err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusNotFound, err.Error())
		return
	}
	if err != nil {
		glog.Errorf("get %v from kubernetes failed, err: %v", kind, err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusInternalServerError, err.Error())
		return
	}
	if !isAllowedOperation(info, largestRole, username, userID, groupID) {
		errorMessage := fmt.Sprintf("no permission to update %v %v/%v", kind, namespace, name)
		glog.Errorf("%v, group: %v, creator: %v/%v, request user: %v/%v, user group: %v, user role: %v", errorMessage,
			groupID, username, userID, info.Name, info.UserID, info.Group.ID, largestRole)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, errorMessage)
		return
	}

	requestBody, err := ioutil.ReadAll(r.Body)
	if err != nil {
		glog.Errorf("request body read failed, %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, fmt.Sprintf("request body read failed, %v", err))
		return
	}

	newRequestBody, err := reconstruction(old, requestBody, username, userID, groupID)
	if err != nil {
		glog.Errorf("reconstruction request body failed, %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusBadRequest, fmt.Sprintf("reconstruction request body failed, %v", err))
		return
	}

	req, err := http.NewRequest(r.Method, r.URL.String(), bytes.NewBuffer(newRequestBody))
	if err != nil {
		glog.Errorf("construct request failed, %v", err)
		util.ReturnErrorResponseInResponseWriter(w, http.StatusInternalServerError, fmt.Sprintf("construct request failed, %v", err))
		return
	}
	req.RequestURI = r.RequestURI
	req.Header.Set("Content-Type", "application/merge-patch+json")

	glog.Infof("start update %v, name: %v, user: %v/%v, user role: %v", kind, name, info.Name, info.UserID, largestRole)
	(&KubernetesController{}).ServeHTTP(w, req)
	return
}
